package xyz.xtt.asset.service.impl;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.tongtong.middleware.vo.params.MetaDataAttribute;
import com.tongtong.middleware.vo.params.MetaDataParams;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.asset.api.enums.AssetNoStatusEnum;
import xyz.xtt.asset.api.enums.AssetNoTypeEnum;
import xyz.xtt.asset.api.enums.AssetTypeEnum;
import xyz.xtt.asset.api.enums.BizTypeEnum;
import xyz.xtt.asset.api.enums.OwnerTypeEnum;
import xyz.xtt.asset.api.enums.ShowStateEnum;
import xyz.xtt.asset.api.enums.StatusAllEnum;
import xyz.xtt.asset.client.request.MintDataVo;
import xyz.xtt.asset.client.request.MintRequest;
import xyz.xtt.asset.client.response.MintResponse;
import xyz.xtt.asset.constants.AssetCache;
import xyz.xtt.asset.constants.ErrorMessage;
import xyz.xtt.asset.entity.TblAsset;
import xyz.xtt.asset.entity.TblAssetExt;
import xyz.xtt.asset.entity.TblAssetNo;
import xyz.xtt.asset.entity.TblAssetStaticResource;
import xyz.xtt.asset.entity.TblThirdOrder;
import xyz.xtt.asset.enums.OrderTypeEnum;
import xyz.xtt.asset.enums.ThirdOrderTypeEnum;
import xyz.xtt.asset.enums.UserType;
import xyz.xtt.asset.request.RoseOrderParam;
import xyz.xtt.asset.request.RoseOrderRequest;
import xyz.xtt.asset.service.IInnerService;
import xyz.xtt.asset.service.INftService;
import xyz.xtt.asset.service.IRoseService;
import xyz.xtt.asset.service.ITblAssetExtService;
import xyz.xtt.asset.service.ITblAssetNoService;
import xyz.xtt.asset.service.ITblAssetService;
import xyz.xtt.asset.service.ITblAssetStaticResourceService;
import xyz.xtt.asset.service.ITblThirdOrderService;
import xyz.xtt.asset.utils.RSAUtil;
import xyz.xtt.asset.vo.BusinessAttachVo;
import xyz.xtt.common.asset.AssertUtils;
import xyz.xtt.common.starter.cache.CacheManager;
import xyz.xtt.pay.client.response.BusinessOrderResponse;
import xyz.xtt.pay.client.response.TblPayOrderDto;
import xyz.xtt.user.client.request.UserRegRequest;
import xyz.xtt.user.client.response.UserInfoResponse;
import xyz.xtt.user.client.response.UserRegResponse;

/**
 * @author huqibo
 * @date 2025/02/10
 */
@Slf4j
@RefreshScope
@Service
public class IRoseServiceImpl implements IRoseService {
	@Autowired
	private ITblAssetService iTblAssetService;
	@Autowired
	private ITblAssetExtService iTblAssetExtService;
	@Autowired
	private ITblAssetStaticResourceService iTblAssetStaticResourceService;
	@Autowired
	private INftService iNftService;
	@Autowired
	private CacheManager cacheManager;

	@Value("${asset.rose.fakemin:5}")
	private Long fakeMin;

	@Value("${asset.rose.fakemax:8}")
	private Long fakeMax;

	@Value("${asset.rose.actlimit:1000}")
	private Long actLimit;

	@Value("${asset.rose.configId:1000}")
	private String[] roseConfigId;

	@Value("${asset.rose.sercret}")
	private String roseSercret;

	@Value("${asset.rose.publicKey}")
	private String rosePublicKey;

	@Value("${asset.rose.privateKey}")
	private String rosePrivateKey;

	@Value("${rocketmq.consumer.third.rose.order}")
	private String roseThirdTopic;

	@Autowired
	private IInnerService iInnerService;
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	private ITblThirdOrderService iTblThirdOrderService;
	@Autowired
	private RocketMQTemplate rocketMQTemplate;
	@Autowired
	private ITblAssetNoService iTblAssetNoService;
	
	@Transactional(rollbackFor = Exception.class)
	@Override
	public MintResponse rosePayAfter(TblPayOrderDto payOrderDto, boolean third) {
		String businessAttachStr = payOrderDto.getBusinessAttach();
		if (StringUtils.isBlank(businessAttachStr)) {
			log.info("rosePayAfter  businessAttach is blank payOrderDto:{}", JSONUtil.toJsonStr(payOrderDto));
			return null;
		}
		BusinessAttachVo businessAttach = JSONUtil.toBean(businessAttachStr, BusinessAttachVo.class);
		if (!businessAttach.isNotBlank()) {
			log.info("rosePayAfter  businessAttach is blank payOrderDto:{}", JSONUtil.toJsonStr(payOrderDto));
			return null;
		}
		TblAsset asset = iTblAssetService.info(businessAttach.getGoodsId());
		if (Objects.isNull(asset)) {
			log.info("rosePayAfter  asset is null payOrderDto:{}", JSONUtil.toJsonStr(payOrderDto));
			return null;
		}
		Long assetId = asset.getId();
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(assetId);
		TblAssetStaticResource assetStatic = iTblAssetStaticResourceService.infoWithAssetIdByCache(assetId);
		AssetTypeEnum assetType = AssetTypeEnum.queryConvert(asset.getFirstType());
		String metadata = assetExt.getMetadata();
		MetaDataParams bean = JSONUtil.toBean(metadata, MetaDataParams.class);
		List<MetaDataAttribute> attributes = Lists.newArrayList();
		if (Objects.nonNull(bean)) {
			attributes = bean.getAttributes();
		}
		List<MintDataVo> mintAttr = Lists.newArrayList();
		for (MetaDataAttribute att : attributes) {
			String trait_type = att.getTrait_type();
			Object value = att.getValue();
			mintAttr.add(new MintDataVo(trait_type, String.valueOf(value)));
		}
		Long parentId = Longs.tryParse(asset.getGoodsId());
		Long nextSeqNo = iTblAssetService.nextSeqNoFake(parentId);
		MintRequest mintRequest = MintRequest
				.builder()
				.description(asset.getDescription())
				.externalUrl(assetStatic.getFileUri())
				.image(assetStatic.getDisplayUri())
				.name(asset.getAssetName())
				.attributes(mintAttr)
				.authorId(asset.getAuthorId())
				.ownerId(String.valueOf(payOrderDto.getAppUserId()))
				.referProtocol(assetExt.getReferProtocol())
				.assetType(assetType.name())
				.bizId(IdUtil.getSnowflakeNextIdStr())
				.chainId(Ints.tryParse(assetExt.getChainId()))
				.amount(1)
				.price(asset.getPrice())
				.unit(asset.getUnit())
				.bizType(BizTypeEnum.NFT.dbType())
				.sellEndDatetime(assetExt.getSellStartDatetime())
				.sellEndDatetime(assetExt.getSellEndDatetime())
				.showState(ShowStateEnum.DOWN.code())
				.ownerType(OwnerTypeEnum.PLATFORM.code())
				.parentId(parentId)
				.seqNo(nextSeqNo)
				.rankable(Objects.isNull(asset.getRankable()) ? StatusAllEnum.YES.code() : asset.getRankable())
				.sellable(Objects.isNull(asset.getSellable()) ? StatusAllEnum.YES.code() : asset.getSellable())
				.giftable(Objects.isNull(asset.getGiftable()) ? StatusAllEnum.YES.code() : asset.getGiftable())
				.build();
		log.info("rosePayAfter mint request:{}",JSONUtil.toJsonStr(mintRequest));
		MintResponse mint = iNftService.mint(mintRequest, OrderTypeEnum.NORMAL);
		AssertUtils.isNull(mint, ErrorMessage.MINT_EX);
		Long transactionOrderId = mint.getTransactionOrderId();
		if (third) {
			iTblAssetService.updateSeqNo(parentId, 1L);
		} else {
			boolean mintAfterUpdateResult = iInnerService.mintAfterUpdateResult(transactionOrderId, transactionOrderId);
			AssertUtils.isFalse(mintAfterUpdateResult, ErrorMessage.MINT_EX, () -> log.info("transactionOrderId:{}", transactionOrderId));
			log
					.info("rosePayAfter payOrderDto:{}, transactionOrderId:{},mintAfterUpdateResult:{}", JSONUtil
							.toJsonStr(payOrderDto), transactionOrderId, mintAfterUpdateResult);
			// 超过1000, 用直实的销量,不在虚增销量
			if (nextSeqNo.longValue() > actLimit) {
				iTblAssetService.updateSeqNo(parentId, 1L);
			} else {
				if (fakeMax.longValue() > fakeMin.longValue()) {
					Long fakeRdm = ThreadLocalRandom.current().nextLong(fakeMin, fakeMax);
					Long fakeVal = 1 + fakeRdm;
					iTblAssetService.updateSeqNo(parentId, fakeVal);
				} else {
					iTblAssetService.updateSeqNo(parentId, 1L);
				}
			}
		}

		return mint;
	}

	@SuppressWarnings("unchecked")
	private RoseOrderParam thirdOrderBeforeCheck(String data) {
		AssertUtils.isTrue(StringUtils.isBlank(data), ErrorMessage.THIRD_RECEIVE_ISBLANK);
		RSA rsa = new RSA(rosePrivateKey, rosePublicKey);
		byte[] decrypt = rsa.decrypt(data, KeyType.PrivateKey);
		String jsonStr = StrUtil.str(decrypt, StandardCharsets.UTF_8);
		RoseOrderParam param = JSONUtil.toBean(jsonStr, RoseOrderParam.class);
		AssertUtils.isNull(param, ErrorMessage.THIRD_PARAM_ISNULL);
		log.info("RoseOrderParam param:{}", JSONUtil.toJsonStr(param));
		AssertUtils.isFalse(StringUtils.length(param.getPhonenumber()) == 11, ErrorMessage.THIRD_ORDER_PHONR_EX);
		boolean isValid = Validator.isMobile(param.getPhonenumber());
		AssertUtils.isFalse(isValid, ErrorMessage.THIRD_ORDER_PHONR_EX);
		Map<String, Object> bizParams = JSONUtil.toBean(jsonStr, Map.class);
		bizParams.remove("sign");
		String generateSign = RSAUtil.generateSign(roseSercret, bizParams);
		// 校验签名
		AssertUtils.isFalse(Objects.equals(generateSign, param.getSign()), ErrorMessage.THIRD_ORDER_SIGN_EX);
		boolean anyMatch = Arrays.stream(roseConfigId).anyMatch(x -> StringUtils.equals(param.getConfigId(), x));
		AssertUtils.isFalse(anyMatch, ErrorMessage.THIRD_ORDER_CONFIG_EX);
		TblAsset info = iTblAssetService.info(param.getConfigId());
		log.info("RoseOrderParam TblAsset info:{}", JSONUtil.toJsonStr(info));
		AssertUtils.isNull(info, ErrorMessage.ASSET_NOT_EXIST);
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(info.getId());
		AssertUtils.isNull(assetExt, ErrorMessage.ASSET_NOT_EXIST);
		AssertUtils.isFalse(assetExt.checkTime(), ErrorMessage.ASSERT_SELL_DATE_PERIOD);
		TblThirdOrder dbOrder = iTblThirdOrderService.infoCahceWithOrderNumer(param.getOrderNumber());
		AssertUtils.nonNull(dbOrder, ErrorMessage.THIRD_ORDER_EXIST);
		return param;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean thirdOrder(RoseOrderRequest roseOrder) {
		RoseOrderParam param = thirdOrderBeforeCheck(roseOrder.getData());
		return roseOrder(param);
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public Boolean thirdOrderMsgAfter(TblThirdOrder order) {
		String orderNumber = order.getOrderNumber();
		TblThirdOrder dbOrder = iTblThirdOrderService.infoCahceWithOrderNumer(orderNumber);
		if (Objects.isNull(dbOrder)) {
			log.info("thirdOrderMsgAfter is null:{}", JSONUtil.toJsonStr(order));
			return Boolean.FALSE;
		}
		Integer result = dbOrder.getResult();
		if (Objects.equals(result, 1)) {
			log.info("thirdOrderMsgAfter received:{}", JSONUtil.toJsonStr(order));
			return Boolean.FALSE;
		}
		Pair<UserType, Long> user = userInfo(dbOrder.getPhonenumber(), dbOrder.getUserName());
		Long uid = user.getValue();
		BusinessAttachVo businessAttach = BusinessAttachVo.builder().assetId(Longs.tryParse(dbOrder.getConfigId())).goodsId(dbOrder.getConfigId()).build();
		TblPayOrderDto payOrderDto = TblPayOrderDto
				.builder()
				.appUserId(user.getValue())
				.businessRefId(dbOrder.getConfigId())
				.businessAttach(JSONUtil.toJsonStr(businessAttach))
				.build();
		MintResponse rosePayAfter = rosePayAfter(payOrderDto, true);
		AssertUtils.isNull(rosePayAfter, ErrorMessage.THIRD_ORDER_MSG_EX);
		Boolean updateResult = iTblThirdOrderService.updateResult(orderNumber, uid, 1, rosePayAfter.getTransactionOrderId());
		AssertUtils.isFalse(updateResult, ErrorMessage.THIRD_ORDER_SAVE_EX);
		return Objects.nonNull(rosePayAfter);
	}

	private Pair<UserType, Long> userInfo(String mobile, String nickName) {
		UserInfoResponse userInfoWithPhone = this.iInnerService.userInfoWithPhone(mobile);
		if (Objects.nonNull(userInfoWithPhone)) {
			return new Pair<UserType, Long>(UserType.OLD_USER, userInfoWithPhone.getAppUserId());
		}
		UserRegRequest userRegRequest = UserRegRequest.builder().name(nickName).phone(mobile).build();
		UserRegResponse userReg = iInnerService.register(userRegRequest);
		return new Pair<UserType, Long>(UserType.NEW_REG, userReg.getAppUid());
	}

	@Override
	public Boolean after(String orderNumber) {
		TblThirdOrder order = iTblThirdOrderService.infoWithOrderNumer(orderNumber);
		AssertUtils.isNull(order, ErrorMessage.THIRD_ORDER_SAVE_EX);
		String jsonString = JSONUtil.toJsonStr(order);
		String message = JSON.toJSONString(order);
		String topic = String.format("%s:%s", roseThirdTopic, ThirdOrderTypeEnum.ROSE.name());
		rocketMQTemplate.convertAndSend(topic, message);
		return Objects.nonNull(order);
	}

	@Override
	public Boolean roseOrder(RoseOrderParam param) {
		TblThirdOrder order = TblThirdOrder
				.builder()
				.id(IdUtil.getSnowflakeNextId())
				.phonenumber(param.getPhonenumber())
				.userName(param.getUserName())
				.configId(param.getConfigId())
				.orderNumber(param.getOrderNumber())
				.totalAmount(param.getTotalAmount())
				.bizType(ThirdOrderTypeEnum.ROSE.name())
				.userId(0L)
				.userType(0)
				.result(0)
				.crt(LocalDateTime.now())
				.build();
		boolean save = iTblThirdOrderService.save(order);
		AssertUtils.isFalse(save, ErrorMessage.THIRD_ORDER_SAVE_EX);
		String jsonString = JSONUtil.toJsonStr(order);
		AssetCache tblThirdOrder = AssetCache.TBL_THIRD_ORDER;
		stringRedisTemplate.opsForValue().set(tblThirdOrder.genKey(order.getOrderNumber()), jsonString, tblThirdOrder.ttl(), tblThirdOrder.timeUnit());
		String message = JSON.toJSONString(order);
		String topic = String.format("%s:%s", roseThirdTopic, ThirdOrderTypeEnum.ROSE.name());
		rocketMQTemplate.convertAndSend(topic, message);
		log.info("thirdOrder sendMessage topic:{},message:{}", topic, message);
		return Boolean.TRUE;
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public MintResponse mintRoseAfterPay(BusinessOrderResponse businessOrder) {
		long assetId = Long.parseLong(businessOrder.getGoodsId());
		TblAsset asset = iTblAssetService.infoWithIdByCache(assetId);
		if (Objects.isNull(asset)) {
			log.info("rosePayAfter  asset is null businessOrder:{}", JSONUtil.toJsonStr(businessOrder));
			return null;
		}
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(assetId);
		TblAssetStaticResource assetStatic = iTblAssetStaticResourceService.infoWithAssetIdByCache(assetId);
		AssetTypeEnum assetType = AssetTypeEnum.queryConvert(asset.getFirstType());
		String metadata = assetExt.getMetadata();
		MetaDataParams bean = JSONUtil.toBean(metadata, MetaDataParams.class);
		List<MetaDataAttribute> attributes = Lists.newArrayList();
		if (Objects.nonNull(bean)) {
			attributes = bean.getAttributes();
		}
		List<MintDataVo> mintAttr = Lists.newArrayList();
		for (MetaDataAttribute att : attributes) {
			String trait_type = att.getTrait_type();
			Object value = att.getValue();
			mintAttr.add(new MintDataVo(trait_type, String.valueOf(value)));
		}
		Long parentId = Longs.tryParse(asset.getGoodsId());
		// 获取已经购买到的编号
		long transactionId = Long.parseLong(businessOrder.getBusinessRefId());
		List<TblAssetNo> assetNos = iTblAssetNoService.getAssetNoByTrans(transactionId, AssetNoTypeEnum.ASSET, AssetNoStatusEnum.NORMAL);
		Long nextSeqNo = Long.valueOf(assetNos.get(0).getAssetNo());
		MintRequest mintRequest = MintRequest
				.builder()
				.description(asset.getDescription())
				.externalUrl(assetStatic.getFileUri())
				.image(assetStatic.getDisplayUri())
				.name(asset.getAssetName())
				.attributes(mintAttr)
				.rootAssetId(asset.getRootAssetId())
				.authorId(String.valueOf(businessOrder.getAppUserId()))
				.ownerId(String.valueOf(businessOrder.getAppUserId()))
				.referProtocol(assetExt.getReferProtocol())
				.assetType(assetType.name())
				.bizId(IdUtil.getSnowflakeNextIdStr())
				.chainId(Ints.tryParse(assetExt.getChainId()))
				.amount(1)
				.price(asset.getPrice())
				.unit(asset.getUnit())
				.bizType(BizTypeEnum.NFT.dbType())
				.sellEndDatetime(assetExt.getSellStartDatetime())
				.sellEndDatetime(assetExt.getSellEndDatetime())
				.showState(ShowStateEnum.DOWN.code())
				.ownerType(OwnerTypeEnum.PLATFORM.code())
				.parentId(parentId)
				.seqNo(nextSeqNo)
				.rankable(Objects.isNull(asset.getRankable()) ? StatusAllEnum.YES.code() : asset.getRankable())
				.sellable(Objects.isNull(asset.getSellable()) ? StatusAllEnum.YES.code() : asset.getSellable())
				.giftable(Objects.isNull(asset.getGiftable()) ? StatusAllEnum.YES.code() : asset.getGiftable())
				.build();
		log.info("rosePayAfter mint request:{}", JSONUtil.toJsonStr(mintRequest));
		MintResponse mint = iNftService.mint(mintRequest, OrderTypeEnum.NORMAL);
		AssertUtils.isNull(mint, ErrorMessage.MINT_EX);

		// 铸造完成后将资产id更新
		for (TblAssetNo assetNo : assetNos) {
			assetNo.setAssetId(mint.getAssetId());
			assetNo.setUpdateTime(System.currentTimeMillis());
		}
		iTblAssetNoService.updateBatchById(assetNos);

		// 超过1000, 用直实的销量,不在虚增销量
		if (nextSeqNo.longValue() > actLimit) {
			iTblAssetService.updateSeqNo(parentId, 1L);
		} else {
			if (fakeMax.longValue() > fakeMin.longValue()) {
				Long fakeRdm = ThreadLocalRandom.current().nextLong(fakeMin, fakeMax);
				Long fakeVal = 1 + fakeRdm;
				iTblAssetService.updateSeqNo(parentId, fakeVal);
			} else {
				iTblAssetService.updateSeqNo(parentId, 1L);
			}
		}

		return mint;
	}
}
